package jee.boot.module.oauth2;

import com.fasterxml.jackson.databind.ObjectMapper;
import jee.boot.common.properties.PermitUrlProperties;
import jee.boot.module.oauth2.auth.CustomPasswordTokenGranter;
import jee.boot.module.oauth2.config.OauthLogoutHandler;
import jee.boot.module.oauth2.config.RedisAuthorizationCodeServices;
import jee.boot.oauth.config.exception.CustomOauthException;
import jee.boot.oauth.config.handler.AuthExceptionEntryPoint;
import jee.boot.oauth.config.token.CustomTokenExtractor;
import jee.boot.oauth.config.token.RedisTemplateTokenStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * OAuth2Server 配置
 */
@Configuration
public class OAuth2ServerConfig {

    private static final Logger log = LoggerFactory.getLogger(OAuth2ServerConfig.class);

    @Autowired
    private AuthenticationManager authenticationManager;

    @Resource
    private DataSource dataSource;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Bean
    public RandomValueAuthorizationCodeServices authorizationCodeServices() {
        RedisAuthorizationCodeServices redisAuthorizationCodeServices = new RedisAuthorizationCodeServices();
        redisAuthorizationCodeServices.setRedisTemplate(redisTemplate);
        log.info("加载AuthorizationCodes生成策略完成");
        return redisAuthorizationCodeServices;
    }

    @Bean
    public WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {
            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
                e.printStackTrace();
                OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
                return ResponseEntity.status(oAuth2Exception.getHttpErrorCode()).body(new CustomOauthException(oAuth2Exception.getMessage(), oAuth2Exception));
            }

        };
    }

    @Bean
    public OauthLogoutHandler oauthLogoutHandler() {
        return new OauthLogoutHandler();
    }

    /**
     * 自定义认证类
     *
     * @param tokenServices
     * @param clientDetailsService
     * @param requestFactory
     * @return
     */
    private List<TokenGranter> getTokenGranters(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        return new ArrayList<>(Arrays.asList(
                new CustomPasswordTokenGranter(authenticationManager,
                        tokenServices,
                        clientDetailsService,
                        requestFactory,
                        redisTemplate)
        ));
    }

    /**
     * 认证服务器配置
     */
    @Component
    @Configuration
    @EnableAuthorizationServer
    @AutoConfigureAfter(AuthorizationServerEndpointsConfigurer.class)
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;

        @Resource
        private ObjectMapper objectMapper;

        @Autowired
        private UserDetailsService userDetailsService;

        @Autowired
        private RedisTemplateTokenStore redisTokenStore;

        @Autowired
        private WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator;

        @Autowired
        private RandomValueAuthorizationCodeServices authorizationCodeServices;

        /**
         * 配置身份认证器
         */
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            List<TokenGranter> tokenGranters = getTokenGranters(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
            tokenGranters.add(endpoints.getTokenGranter());
            // @formatter:off
            endpoints
                    //配置code存储策略
                    .tokenStore(redisTokenStore)
                    //配置认证管理器
                    .authenticationManager(authenticationManager)
                    //配置用户查询策略
                    .userDetailsService(userDetailsService)
                    //配置code生成策略
                    .authorizationCodeServices(authorizationCodeServices)
                    //配置web异常响应处理器
                    .exceptionTranslator(webResponseExceptionTranslator)
                    // 自定义机制
                    .tokenGranter(new CompositeTokenGranter(tokenGranters));
            log.info("配置身份认证器完成");
            // @formatter:on

        }

        /**
         * 配置OAuth2的客户端相关信息
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 自定义客户端管理
            clients.withClientDetails(new JdbcClientDetailsService(dataSource));
            log.info("配置客户端信息完成");
        }

        /**
         * 配置安全认证的相关信息
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            // @formatter:off
            security
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()")
                    .allowFormAuthenticationForClients();
            log.info("配置安全认证完成");
            // @formatter:on
        }
    }

    /**
     * 资源服务器配置
     *
     * @author zhilei.wang
     */
    @Configuration
    @EnableResourceServer
    @EnableConfigurationProperties(PermitUrlProperties.class)
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

        @Autowired
        private PermitUrlProperties permitUrlProperties;
        @Autowired
        private AccessDeniedHandler accessDeniedHandler;

        public void configure(WebSecurity web) throws Exception {

        }

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.accessDeniedHandler(accessDeniedHandler);
            resources.tokenExtractor(new CustomTokenExtractor(permitUrlProperties));//对获取token的请求取消token的校验
            resources.authenticationEntryPoint(new AuthExceptionEntryPoint());//认证异常后的信息重写
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers(permitUrlProperties.getOauthUrls()).permitAll()
                    .anyRequest().authenticated()
                    .and().headers().frameOptions().disable();
            // @formatter:on
        }
    }
}
